#include <physical_mm.h>
#include <sys/types.h>
#include <debug.h>

#include "pm.h"

/// memset, sets a range of memory
void memset( void* ptr, unsigned char dat, int len )
{
	char* tmp = (char*) ptr;
	for( int i = 0; i < len; i++ )
		*tmp++ = dat;
}

/// memcpy, copies a block of data
extern "C" void* memcpy( void *dest, const void *src, int n )
{
	char *dst=(char*)dest,*source=(char*)src;
	for( ; n ; n-- )
		*dst++ = *source++;
	return dest;
}

/*
 * Copyright (C) 1998-2001, Freedows
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/// A memory block
typedef struct MemBlock_t {
	uint_t			used : 1;
	uint_t				size;
	struct MemBlock_t*	next;
} MemBlock;

/// The location of the end of the kernel's image
extern char end;

/// The kernel heap
volatile static MemBlock* kheap;

/// Kernel's malloc
void* PhysicalMemoryManager::kmalloc(uint_t size )
{
	// blocks for use later
	volatile MemBlock* mblk;
	MemBlock* newblk;
	char* cblk;
	uint_t size2;
	
	// we can't allocate nothing!
	if( size == 0 ) {
		return 0;
	}
	// the real size
	size2 = size + sizeof( MemBlock );
	
	// is there a heap?
	if( kheap == (MemBlock*) 0 )
	{
		// we want 100 pages (note: this can be reduced to 10 later)
		kheap = mblk = (MemBlock*) pm_alloc( 100 );
		
		// set it up properly
		mblk->used = 0;
		mblk->size = 100 * 4096;
		
		// no next block
		mblk->next = (MemBlock*) 0;
	}
	
	// try to find a free block
	for( mblk = kheap; mblk != (MemBlock*) 0; mblk = mblk->next )
	{
		if( ( !mblk->used ) && ( size2 <= mblk->size ) )
			break;
	}
	
	// see if one was found
	if( mblk == (MemBlock*) 0 )
		return 0;
	
	// set up the block
	cblk = (char*) mblk + size2;
	newblk = (MemBlock*) cblk;
	
	// point to a new empty block, and init it
	newblk->used = 0;
	newblk->size = mblk->size - size2;
	newblk->next = mblk->next;
	
	// point to old block and init it
	mblk->used = 1;
	mblk->size = size;
	mblk->next = newblk;
	
	// return the pointer to the memory
	cblk = (char*) mblk;
	return ((void*) (cblk + sizeof( MemBlock )));
}

/// Kernel's free
void PhysicalMemoryManager::kfree( void* block )
{
	// blocks
	volatile MemBlock* mblk;
	char* cblk;
	
	// get the real block address
	cblk = (char*) block - sizeof( MemBlock );
	
	// find this address in the list
	for( mblk = kheap; mblk != (MemBlock*) 0; mblk = mblk->next )
	{
		if( mblk == (MemBlock*) cblk )
			break;
	}
	
	// bad heap pointer
	if( mblk == (MemBlock*) 0 )
	{
		//kprintf( "[MEM ] kfree called, but invalid pointer (%d)\n", (uint_t) block );
		return;
	}
	
	// free the block
	mblk->used = 0;
	
	// this is the hard part: unlinking this block
	for( mblk = kheap; mblk != (MemBlock*) 0; mblk = mblk->next )
	{
		// find one that isn't used
		while( ( !mblk->used ) && ( mblk->next != (MemBlock*) 0 ) && ( !mblk->next->used ) )
		{
			// resize this block
			mblk->size = (int) mblk->next - (int) mblk + mblk->next->size;
			
			// merge with the next block
			mblk->next = mblk->next->next;
		}
	}
}

/// Kernel's realloc
void* PhysicalMemoryManager::krealloc( void* blk, uint_t size )
{
	// blocks
	MemBlock* mblk;
	void* newblk;
	char* cblk;
	
	// check to see if the block we're given is valid
	if( blk == (MemBlock*) 0 )
		return kmalloc( size );
	
	// check for a valid size, if invalid free the block
	if( size == 0 )
		kfree( blk );
	
	// allocate the new block
	newblk = kmalloc( size );
	
	// check that it's valid
	if( newblk == (MemBlock*) 0 )
		return 0;
	
	// point to the data in the old block
	cblk = (char*) blk - sizeof( MemBlock );
	mblk = (MemBlock*) cblk;
	
	// copy the data
	memcpy( newblk, blk, mblk->size );
	
	// release the old block
	kfree( blk );
	
	// return it
	return newblk;
}
